// IRCSockets.dll  - RFC 1483 Implementation on .Net Sockets
//Copyleft 2006  Pierre-Alain DAVID

//This library is free software; you can redistribute it and/or
//modify it under the terms of the GNU Lesser General Public
//License as published by the Free Software Foundation; either
//version 2.1 of the License, or (at your option) any later version.

//This library is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//Lesser General Public License for more details.

//You should have received a copy of the GNU Lesser General Public
//License along with this library; if not, write to the Free Software
//Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

using System;
using System.Net.Sockets;
using System.Threading.Tasks;
using IrcNetLib.Ping;

namespace IrcNetLib
{

    /// <summary>
    /// This Class represent an IRC Connector.
    /// </summary>
    public sealed class IrcClient : System.Object
    {

        /*
         *  Public Members */

        public string Hostname;
        public int Port;
        public string Receivedlast;
        public string Sendlast;
        public ConnectionState State;

        public bool Connected
        {
            get
            {
                return (State == ConnectionState.Connected);

            }
            set
            {
                if (value)
                    State = ConnectionState.Connected;
            }

        }

        /*------------------------------------------*/
        /*   private members  */

        private static string _crlf = "\r\n";
        private string _localcharset;
        /// <summary>
        /// Return Basic informations
        /// </summary>
        /// <returns>string  hostname:port</returns>
        public override string ToString()
        {
            return Hostname + ":" + Port.ToString();
        }

        /// <summary>
        /// get or set the used Charset.
        /// </summary>
        public string GlobalCharset
        {
            get
            {
                return _localcharset;
            }
            set
            {
                switch (value)
                {
                    case "iso-8859-1":
                    case "UTF-8":
                    case "iso-8859-2":
                    case "iso-8859-3":
                    case "iso-8859-4":
                    case "iso-8859-5":
                    case "iso-8859-6":
                    case "iso-8859-7":
                    case "iso-8859-8":
                    case "iso-8859-9":
                    case "iso-8859-10":
                        _localcharset = value;
                        break;
                    default:
                        _localcharset = "iso-8859-1";
                        break;
                }

            }
        }
        private TcpClient _tcpClient;
        private NetworkStream _stream;


        /// <summary>
        /// Defaut constructor
        /// </summary>
        /// <param name="host">hostname, IP format</param>
        /// <param name="_port">port used</param>
        public IrcClient(string host, int _port)
        {
            Hostname = host;
            Port = _port;
            this._tcpClient = new TcpClient();
            Connected = false;
            _localcharset = "iso-8859-1";
        }

        /// <summary>
        /// return hostname
        /// </summary>
        /// <returns></returns>
        public string GetLocalAdress()
        {
            return System.Net.Dns.GetHostName();
        }

        /// <summary>
        /// Connect to irc server
        /// </summary>
        /// <returns>the connexion was successful or not</returns>
        public async Task Connect(string password = "")
        {
            try
            {
                await ThreadedConnect(password);

            }

            catch (SocketException)
            {


                throw new IRCexeption("Socket connection Error");
            }
            catch (ArgumentException)
            {

            }
        }
        private async Task ThreadedConnect(string password = "")
        {
            SocketsArgs e = new SocketsArgs("connecting");
            OnConnecting(e);
            await _tcpClient.ConnectAsync(Hostname, Port);

            try
            {
                ConnectedArgs f = new ConnectedArgs("ClientConnected", password);
                _stream = this._tcpClient.GetStream();
                Connected = true;
                OnClientConnected(f);



            }
            catch (Exception ex)
            {
                SocketsArgs fArgs = new SocketsArgs("Socket Error");
                Connected = false;
                OnClientUnableConnect( fArgs);

            }
        }



        public bool Disconnect()
        {
            if (Connected)
            {
                try
                {
                    SendIrcCmd("QUIT", "", "YaP!");
                    _tcpClient.Close();
                    ClientDisconnected(this, new SocketsArgs("Client Requested"));
                    Connected = false;
                    return true;
                }
                catch (SocketException)
                {
                    return false;
                }

            }


            return false;
        }


        public void SendIrcCmd(string cmd, string dest, string msg)
        {
            if (Connected)
            {
                string tosend = " ";
                dest = dest.TrimStart((char)37);
                dest = dest.TrimStart((char)33);
                dest = dest.TrimStart((char)64);
                dest = dest.TrimStart((char)43);
                switch (cmd)
                {


                    case "LIST":
                        tosend = cmd + _crlf;
                        break;
                    case "TOPIC":
                        tosend = cmd + " " + dest + " :" + msg + _crlf;
                        break;
                    case "KICK":
                        string[] args = msg.Split("\t".ToCharArray());
                        tosend = cmd + " " + dest + " " + args[0] + " " + args[1] + _crlf;
                        break;
                    case "PART":
                        tosend = cmd + " " + msg + _crlf;
                        break;
                    case "NOTICE":
                        tosend = cmd + " " + dest + " :" + msg + _crlf;
                        break;
                    case "PONG":
                        tosend = cmd + " " + msg + _crlf;
                        break;
                    case "QUIT":
                        //special case here, we must send the quit command immediatly

                        tosend = cmd + " :" + msg + _crlf;
                        byte[] aenvoyer = System.Text.Encoding.GetEncoding("iso-8859-1").GetBytes(tosend);
                        this.Sendlast = tosend;
                        string[] cutted = tosend.Split((char)32);
                        if ((_tcpClient.Connected) && (_stream.CanWrite))
                            _stream.Write(aenvoyer, 0, aenvoyer.Length);
                        tosend = string.Empty;
                        DataSend(this, new EventSendArgs(cmd, string.Empty, msg));
                        break;
                    case "NICK":
                        tosend = cmd + " " + msg + _crlf;
                        break;
                    case "MODE":
                        tosend = cmd + " " + dest + " " + msg + _crlf;
                        break;
                    case "USER":
                        tosend = cmd + " " + msg + _crlf;
                        break;
                    case "PRIVMSG":
                        tosend = cmd + " " + dest + " :" + msg + _crlf;
                        break;
                    case "JOIN":
                        string tojoin;
                        if (!(msg.StartsWith("#")))
                            tojoin = "#" + msg;
                        else
                            tojoin = msg;
                        tosend = cmd + " " + tojoin + _crlf;
                        break;
                    default:
                        tosend = cmd + " " + msg;
                        break;
                }

                if (tosend != string.Empty)
                {
                    SendRawString(tosend);
                    OnDataSend(new EventSendArgs(cmd, dest, msg));
                }
            }

        }

        public void SendRawString(string tosend)
        {
            if (!(tosend.EndsWith(_crlf)))
                tosend += _crlf;
            try
            {

                byte[] aenvoyer = System.Text.Encoding.GetEncoding("iso-8859-1").GetBytes(tosend);
                this.Sendlast = tosend;
                string[] cutted = tosend.Split((char)32);
                if (Connected)
                    _stream.BeginWrite(aenvoyer, 0, aenvoyer.Length, new AsyncCallback(DebutEnvoyer), null);
                EventArgs f = new EventArgs();
            }
            catch (System.IndexOutOfRangeException ex)
            { }
        }

        /// <summary>
        /// This Method intiate the read loop
        /// </summary>
        public async Task ReceiveText()
        {
            try
            {
                if ((_stream.CanRead) && (_tcpClient.Connected))
                {
                    this._stream = this._tcpClient.GetStream();
                    BuffClass mBuff = new BuffClass();
                    int nbRead = await _stream.ReadAsync(mBuff.BytesBuff, 0, mBuff.BytesBuff.Length);
                    mBuff.BytesRead = nbRead;
                    if (nbRead > 0)
                    {
                        SignalReceive(mBuff);
                    }
                    else
                    {
                        CloseReceive();
                    }

                }
            }
            catch (NullReferenceException)
            { }
        }


        private void SignalReceive(BuffClass buffer)
        {
            //ajoute le message
            //string lusocks = System.Text.Encoding.GetEncoding(localcharset).GetString(this.readbuf);

            while (buffer.StrBuff.EndsWith("\0"))
                buffer.StrBuff.TrimEnd("\0".ToCharArray());


            string message;

            if (buffer.StrBuff.EndsWith("\r\n"))
            {
                message = Receivedlast + buffer.StrBuff;
                Receivedlast = string.Empty;
                SocketsArgs e = new SocketsArgs(message);
                OnReceived(this, e);


            }
            else
            {
                Receivedlast += buffer.StrBuff;

            }
            ReceiveText();
        }

        private void CloseReceive()
        {
            try
            {
                this._tcpClient.Client.DisconnectAsync(new SocketAsyncEventArgs() { });
                this.Receivedlast = "Fermeture socket distante";
                OnClientDisconnected( new SocketsArgs("Fermeture socket distante"));
            }
            catch (SocketException)
            {
                this.Receivedlast = "Socket Error!";
            }
        }
        private void DebutEnvoyer(IAsyncResult ar)
        {
            BuffClass mBuff = (BuffClass)ar.AsyncState;

            this._stream = this._tcpClient.GetStream();
            try
            {
                _stream.EndWrite(ar);
                if (mBuff != null)
                    this.Sendlast = mBuff.StrBuff;

            }
            catch (Exception ex)
            {

            }
        }



        /// <summary>
        /// Tout les evenements inherents a cette putain de classe
        /// </summary>
        /// 
        public delegate void SocketReceivedHandler(object sender, SocketsArgs e);
        public event SocketReceivedHandler OnReceived;

        public delegate void SocketSendHandler(object sender, EventSendArgs e);
        public event SocketSendHandler DataSend;

        public delegate void SocketUnableConnectHandler(object sender, SocketsArgs e);
        public event SocketUnableConnectHandler ClientUnableConnect;

        public delegate void SocketConnectingHandler(object sender, SocketsArgs e);
        public event SocketConnectingHandler ClientConnecting;

        public delegate void SocketConnectedHandler(object sender, ConnectedArgs e);
        public event SocketConnectedHandler ClientConnected;

        public delegate void SocketDisconnectingHandler(object sender, SocketsArgs e);
        public event SocketDisconnectingHandler OnDisconnecting;

        public delegate void SocketDisconnectedHandler(object sender, SocketsArgs e);
        public event SocketDisconnectedHandler ClientDisconnected;

        public delegate void SocketExGeneratedHandler(object sender, SocketsArgs e);
        public event SocketExGeneratedHandler OnExSocket;

        public delegate void OnJoinHandler(object sender, EventJoinArgs e);
        public event OnJoinHandler OnJoin;


        private void OnOnDisconnecting(SocketsArgs e)
        {
            OnDisconnecting?.Invoke(this, e);
        }

        private void OnConnecting(SocketsArgs e)
        {
            ClientConnecting?.Invoke(this, e);
        }

        private void OnClientConnected(ConnectedArgs e)
        {
            ClientConnected?.Invoke(this, e);
        }

        private void OnClientUnableConnect(SocketsArgs e)
        {
            ClientUnableConnect?.Invoke(this, e);
        }

        private void OnDataSend(EventSendArgs e)
        {
            DataSend?.Invoke(this, e);
        }

        private void OnClientDisconnected(SocketsArgs e)
        {
            ClientDisconnected?.Invoke(this, e);
        }
    }
}
